library openblt_can_vector;
//***************************************************************************************
// Project Name: MicroBoot Interface for Borland Delphi
//  Description: XCP - CAN interface for MicroBoot supporting Vector CAN
//    File Name: openblt_can_vector.dpr
//
//---------------------------------------------------------------------------------------
//                          C O P Y R I G H T
//---------------------------------------------------------------------------------------
//   Copyright (c) 2011 by Feaser    http://www.feaser.com    All rights reserved
//
//   This software has been carefully tested, but is not guaranteed for any particular
// purpose. The author does not offer any warranties and does not guarantee the accuracy,
//   adequacy, or completeness of the software and is not responsible for any errors or
//              omissions or the results obtained from use of the software.
//
//---------------------------------------------------------------------------------------
//                            L I C E N S E
//---------------------------------------------------------------------------------------
// This file is part of OpenBLT. OpenBLT is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any later
// version.
//
// OpenBLT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with OpenBLT.
// If not, see <http://www.gnu.org/licenses/>.
//
// A special exception to the GPL is included to allow you to distribute a combined work
// that includes OpenBLT without being obliged to provide the source code for any
// proprietary components. The exception text is included at the bottom of the license
// file <license.html>.
//
//***************************************************************************************


//***************************************************************************************
// Includes
//***************************************************************************************
uses
  Windows,
  Messages,
  Graphics,
  Controls,
  Forms,
  Dialogs,
  SysUtils,
  Classes,
  Extctrls,
  XcpProtection in '..\..\XcpProtection.pas',
  SRecReader in '..\..\SRecReader.pas',
  XcpDataFile in '..\..\XcpDataFile.pas',
  XcpLoader in '..\..\XcpLoader.pas',
  XcpTransport in 'XcpTransport.pas',
  CANdrvD in 'CANdrvD.pas',
  CANlibD in 'CANlibD.pas',
  XcpSettings in 'XcpSettings.pas' {XcpSettingsForm};


//***************************************************************************************
// Global Constants
//***************************************************************************************
const kMaxProgLen	= 256; // maximum number of bytes to progam at one time


//***************************************************************************************
// Type Definitions
//***************************************************************************************
// DLL Interface Callbacks - modifications requires potential update of all interfaces!
type
  TStartedEvent  = procedure(length: Longword) of object;
  TProgressEvent = procedure(progress: Longword) of object;
  TDoneEvent     = procedure  of object;
  TErrorEvent    = procedure(error: ShortString) of object;
  TLogEvent      = procedure(info:  ShortString) of object;
  TInfoEvent     = procedure(info:  ShortString) of object;

type
  TEventHandlers = class // create a dummy class
    procedure OnTimeout(Sender: TObject);
  end;

//***************************************************************************************
// Global Variables
//***************************************************************************************
var
  //--- begin of don't change ---
  AppOnStarted  : TStartedEvent;
  AppOnProgress : TProgressEvent;
  AppOnDone     : TDoneEvent;
  AppOnError    : TErrorEvent;
  AppOnLog      : TLogEvent;
  AppOnInfo     : TInfoEvent;
  //--- end of don't change ---
  timer         : TTimer;
  events        : TEventHandlers;
  loader        : TXcpLoader;
  datafile      : TXcpDataFile;
  progdata      : array of Byte;
  progfile      : string;
  stopRequest   : boolean;


//***************************************************************************************
// NAME:           MbiCallbackOnStarted
// PARAMETER:      length of the file that is being downloaded.
// RETURN VALUE:   none
// DESCRIPTION:    Wrapper function for safely calling an application callback
//
//***************************************************************************************
procedure MbiCallbackOnStarted(length: Longword);
begin
  if Assigned(AppOnStarted) then
  begin
    AppOnStarted(length);
  end;
end; //** end of MbiCallbackOnStarted ***


//***************************************************************************************
// NAME:           MbiCallbackOnProgress
// PARAMETER:      progress of the file download.
// RETURN VALUE:   none
// DESCRIPTION:    Wrapper function for safely calling an application callback
//
//***************************************************************************************
procedure MbiCallbackOnProgress(progress: Longword);
begin
  if Assigned(AppOnProgress) then
  begin
    AppOnProgress(progress);
  end;
end; //** end of MbiCallbackOnProgress ***


//***************************************************************************************
// NAME:           MbiCallbackOnDone
// PARAMETER:      none
// RETURN VALUE:   none
// DESCRIPTION:    Wrapper function for safely calling an application callback
//
//***************************************************************************************
procedure MbiCallbackOnDone;
begin
  if Assigned(AppOnDone) then
  begin
    AppOnDone;
  end;
end; //** end of MbiCallbackOnDone ***


//***************************************************************************************
// NAME:           MbiCallbackOnError
// PARAMETER:      info about the error that occured.
// RETURN VALUE:   none
// DESCRIPTION:    Wrapper function for safely calling an application callback
//
//***************************************************************************************
procedure MbiCallbackOnError(error: ShortString);
begin
  if Assigned(AppOnError) then
  begin
    AppOnError(error);
  end;
end; //** end of MbiCallbackOnError ***


//***************************************************************************************
// NAME:           MbiCallbackOnLog
// PARAMETER:      info on the log event.
// RETURN VALUE:   none
// DESCRIPTION:    Wrapper function for safely calling an application callback
//
//***************************************************************************************
procedure MbiCallbackOnLog(info:  ShortString);
begin
  if Assigned(AppOnLog) then
  begin
    AppOnLog(info);
  end;
end; //** end of MbiCallbackOnLog ***


//***************************************************************************************
// NAME:           MbiCallbackOnInfo
// PARAMETER:      details on the info event.
// RETURN VALUE:   none
// DESCRIPTION:    Wrapper function for safely calling an application callback
//
//***************************************************************************************
procedure MbiCallbackOnInfo(info:  ShortString);
begin
  if Assigned(AppOnInfo) then
  begin
    AppOnInfo(info);
  end;
end; //** end of MbiCallbackOnLog ***


//***************************************************************************************
// NAME:           LogData
// PARAMETER:      pointer to byte array and the data length
// RETURN VALUE:   none
// DESCRIPTION:    Writes the program data formatted to the logfile
//
//***************************************************************************************
procedure LogData(data : PByteArray; len : longword); stdcall;
var
  currentWriteCnt : byte;
  cnt : byte;
  logStr : string;
  bufferOffset    : longword;
begin
  bufferOffset := 0;

	while len > 0 do
	begin
  	// set the current write length optimized to log 32 bytes per line
	  currentWriteCnt := len mod 32;
 		if currentWriteCnt = 0 then currentWriteCnt := 32;
    logStr := '';

    // prepare the line to add to the log
    for cnt := 0 to currentWriteCnt-1 do
    begin
      logStr := logStr + Format('%2.2x ', [data[bufferOffset+cnt]]);
    end;

    // update the log
    MbiCallbackOnLog(logStr);

    // update loop variables
	  len := len - currentWriteCnt;
    bufferOffset := bufferOffset + currentWriteCnt;
	end;
end; //*** end of LogData ***


//***************************************************************************************
// NAME:           OnTimeout
// PARAMETER:      none
// RETURN VALUE:   none
// DESCRIPTION:    Timer event handler. A timer is used in this example to simulate the
//                 progress of a file download. It also demonstrates how to use the
//                 application callbacks to keep the application informed.
//
//***************************************************************************************
procedure TEventHandlers.OnTimeout(Sender: TObject);
var
  errorInfo       : string;
  progress        : longword;
  regionCnt       : longword;
  currentWriteCnt : word;
  sessionStartResult : byte;
  bufferOffset    : longword;
  addr            : longword;
  len             : longword;
  dataSizeKB      : real;
begin
  timer.Enabled := False;

  // connect the transport layer
  MbiCallbackOnInfo('Connecting to the CAN interface.');
  MbiCallbackOnLog('Connecting to the CAN interface. t='+TimeToStr(Time));
  Application.ProcessMessages;
  if not loader.Connect then
  begin
    // update the user info
    MbiCallbackOnError('Could not connect to CAN interface. Check your configuration.');
    MbiCallbackOnLog('Could not connect to CAN interface. Check your configuration and try again. t='+TimeToStr(Time));
    Exit;
  end;

  //---------------- start the programming session --------------------------------------
  MbiCallbackOnLog('Starting the programming session. t='+TimeToStr(Time));

  // try initial connect via XCP. if the user program is able to reactivate the bootloader
  // it will do so now
  sessionStartResult := loader.StartProgrammingSession;
  if sessionStartResult = kProgSessionUnlockError then
  begin
    MbiCallbackOnLog('Security issue. Could not unprotect the programming resource. Check your configured XCP protection DLL. t='+TimeToStr(Time));
    MbiCallbackOnError('Security issue. Could not unprotect the programming resource.');
    loader.Disconnect;
    Exit;
  end;
  // try initial connect via XCP
  if sessionStartResult <> kProgSessionStarted then
  begin
    // update the user info
    MbiCallbackOnInfo('Could not connect. Retrying. Reset your target if this takes a long time.');
    MbiCallbackOnLog('Connect failed. Switching to backdoor entry mode. t='+TimeToStr(Time));
    Application.ProcessMessages;
    // possible that the bootloader is being activated, which means that the target's
    // CAN controller is being reinitialized. We should not send any data on the CAN
    // network for this to finish. 200ms should do it. not that the backdoor entry time
    // should be at least 2.5x this.
    Sleep(200);
    // continuously try to connect via XCP true the backdoor
    sessionStartResult := kProgSessionGenericError;
    while sessionStartResult <> kProgSessionStarted do
    begin
      sessionStartResult := loader.StartProgrammingSession;
      Application.ProcessMessages;
      Sleep(5);
      // if the is in reset of otherwise does not have the CAN controller synchronized to
      // the CAN bus, we will be generating error frames, possibly leading to a bus off.
      // check for this
      if loader.IsComError then
      begin
        // bus off state, so try to recover.
        MbiCallbackOnLog('Communication error detected. Trying automatic recovery. t='+TimeToStr(Time));
        loader.Disconnect;
        if not loader.Connect then
        begin
          MbiCallbackOnLog('Could not connect to CAN interface. Check your configuration and try again. t='+TimeToStr(Time));
          MbiCallbackOnError('Could not connect to CAN interface. Check your configuration.');
          Exit;
        end;
        Sleep(200);
      end;
      // don't retry if the error was caused by not being able to unprotect the programming resource
      if sessionStartResult = kProgSessionUnlockError then
      begin
        MbiCallbackOnLog('Security issue. Could not unprotect the programming resource. Check your configured XCP protection DLL. t='+TimeToStr(Time));
        MbiCallbackOnError('Security issue. Could not unprotect the programming resource.');
        Exit;
      end;

      // check if the user cancelled
      if stopRequest then
      begin
        MbiCallbackOnError('Programming session cancelled by user.');
        Exit;
      end;
    end;
  end;

  // still here so programming session was started
  MbiCallbackOnLog('Programming session started. t='+TimeToStr(Time));

  // create the datafile object
  datafile := TXcpDataFile.Create(progfile);

  // compute the size in kbytes
  dataSizeKB := datafile.GetDataCnt / 1024;

  // Call application callback when we start the actual download
  MbiCallbackOnStarted(datafile.GetDataCnt);

  // Init progress to 0 progress
  progress := 0;
  MbiCallbackOnProgress(progress);

  //---------------- next clear the memory regions --------------------------------------
  // update the user info
  MbiCallbackOnInfo('Erasing memory...');

  for regionCnt := 0 to datafile.GetRegionCnt-1 do
  begin
    // obtain the region info
    datafile.GetRegionInfo(regionCnt, addr, len);

    // erase the memory
    MbiCallbackOnLog('Clearing Memory '+Format('addr:0x%x,len:0x%x',[addr,len])+'. t='+TimeToStr(Time));
    if not loader.ClearMemory(addr, len) then
    begin
      loader.GetLastError(errorInfo);
      MbiCallbackOnLog('Could not clear memory ('+errorInfo+'). t='+TimeToStr(Time));
      MbiCallbackOnError('Could not clear memory ('+errorInfo+').');
      datafile.Free;
      Exit;
    end;
    MbiCallbackOnLog('Memory cleared. t='+TimeToStr(Time));
  end;

  //---------------- next program the memory regions ------------------------------------
  for regionCnt := 0 to datafile.GetRegionCnt-1 do
  begin
    // update the user info
    MbiCallbackOnInfo('Reading file...');

    // obtain the region info
    datafile.GetRegionInfo(regionCnt, addr, len);
    // dynamically allocated buffer memory
    SetLength(progdata, len);
    // obtain the regiond data
    datafile.GetRegionData(regionCnt, progdata);

    bufferOffset := 0;
  	while len > 0 do
  	begin
	  	// set the current write length taking into account kMaxProgLen
		  currentWriteCnt := len mod kMaxProgLen;
  		if currentWriteCnt = 0 then currentWriteCnt := kMaxProgLen;

      // program the data
      MbiCallbackOnLog('Programming Data '+Format('addr:0x%x,len:0x%x',[addr,currentWriteCnt])+'. t='+TimeToStr(Time));
      LogData(@progdata[bufferOffset], currentWriteCnt);

      if not loader.WriteData(addr, currentWriteCnt, @progdata[bufferOffset]) then
      begin
        loader.GetLastError(errorInfo);
        MbiCallbackOnLog('Could not program data ('+errorInfo+'). t='+TimeToStr(Time));
        MbiCallbackOnError('Could not program data ('+errorInfo+').');
        datafile.Free;
        Exit;
      end;
      MbiCallbackOnLog('Data Programmed. t='+TimeToStr(Time));

      // update progress
      progress := progress + currentWriteCnt;
      MbiCallbackOnProgress(progress);

		  // update loop variables
		  len := len - currentWriteCnt;
		  addr := addr + currentWriteCnt;
      bufferOffset := bufferOffset + currentWriteCnt;

      // update the user info
      MbiCallbackOnInfo('Programming data... ' + Format('(%.1n of %.1n Kbytes)',[(progress/1024), dataSizeKB]));

	  end;
  end;

  //---------------- stop the programming session ---------------------------------------
  MbiCallbackOnLog('Stopping the programming session. t='+TimeToStr(Time));
  if not loader.StopProgrammingSession then
  begin
    loader.GetLastError(errorInfo);
    MbiCallbackOnLog('Could not stop the programming session ('+errorInfo+'). t='+TimeToStr(Time));
    MbiCallbackOnError('Could not stop the programming session ('+errorInfo+').');
    datafile.Free;
    Exit;
  end;
  MbiCallbackOnLog('Programming session stopped. t='+TimeToStr(Time));

  // all done so set progress to 100% and finish up
  progress := datafile.GetDataCnt;
  datafile.Free;
  MbiCallbackOnProgress(progress);
  MbiCallbackOnLog('File successfully downloaded t='+TimeToStr(Time));
  MbiCallbackOnDone;

end; //*** end of OnTimeout ***


//***************************************************************************************
// NAME:           MbiInit
// PARAMETER:      callback function pointers
// RETURN VALUE:   none
// DESCRIPTION:    Called by the application to initialize the interface library.
//
//***************************************************************************************
procedure MbiInit(cbStarted: TStartedEvent; cbProgress: TProgressEvent;
                  cbDone: TDoneEvent; cbError: TErrorEvent; cbLog: TLogEvent;
                  cbInfo: TInfoEvent); stdcall;
begin
  //--- begin of don't change ---
  AppOnStarted  := cbStarted;
  AppOnProgress := cbProgress;
  AppOnDone     := cbDone;
  AppOnLog      := cbLog;
  AppOnInfo     := cbInfo;
  AppOnError    := cbError;
  //--- end of don't change ---

  // create xcp loader object
  loader := TXcpLoader.Create;

  // update to the latest configuration
  loader.Configure(ExtractFilePath(ParamStr(0))+'openblt_can_vector.ini');

  // create and init a timer
  events := TEventHandlers.Create;
  timer := TTimer.Create(nil);
  timer.Enabled := False;
  timer.Interval := 100;
  timer.OnTimer  := events.OnTimeout;
end; //*** end of MbiInit ***


//***************************************************************************************
// NAME:           MbiStart
// PARAMETER:      filename of the file that is to be downloaded.
// RETURN VALUE:   none
// DESCRIPTION:    Called by the application to request the interface library to download
//                 the file that is passed as a parameter.
//
//***************************************************************************************
procedure MbiStart(fileName: ShortString); stdcall;
begin
  // update the user info
  MbiCallbackOnInfo('');

  // start the log
  MbiCallbackOnLog('--- Downloading "'+fileName+'" ---');

  // reset stop request
  stopRequest := false;

  // start the startup timer which gives microBoot a chance to paint itself
  timer.Enabled := True;

  // store the program's filename
  progfile := fileName;
end; //*** end of MbiStart ***


//***************************************************************************************
// NAME:           MbiStop
// PARAMETER:      none
// RETURN VALUE:   none
// DESCRIPTION:    Called by the application to request the interface library to stop
//                 a download that could be in progress.
//
//***************************************************************************************
procedure MbiStop; stdcall;
begin
  // set stop request
  stopRequest := true;

  // disconnect the transport layer
  MbiCallbackOnLog('Disconnecting the transport layer. t='+TimeToStr(Time));
  loader.Disconnect;
end; //*** end of MbiStop ***


//***************************************************************************************
// NAME:           MbiDeInit
// PARAMETER:      none
// RETURN VALUE:   none
// DESCRIPTION:    Called by the application to uninitialize the interface library.
//
//***************************************************************************************
procedure MbiDeInit; stdcall;
begin
  // release xcp loader object
  loader.Free;

  // release the timer and events object
  timer.Free;
  events.Free;

  //--- begin of don't change ---
  AppOnStarted  := nil;
  AppOnProgress := nil;
  AppOnDone     := nil;
  AppOnLog      := nil;
  AppOnInfo     := nil;
  AppOnError    := nil;
  //--- end of don't change ---
end; //*** end of MbiDeInit ***


//***************************************************************************************
// NAME:           MbiName
// PARAMETER:      none
// RETURN VALUE:   name of the interface library
// DESCRIPTION:    Called by the application to obtain the name of the interface library.
//
//***************************************************************************************
function MbiName : ShortString; stdcall;
begin
  Result := 'OpenBLT CAN Vector';
end; //*** end of MbiName ***


//***************************************************************************************
// NAME:           MbiDescription
// PARAMETER:      none
// RETURN VALUE:   description of the interface library
// DESCRIPTION:    Called by the application to obtain the description of the interface
//                 library.
//
//***************************************************************************************
function MbiDescription : ShortString; stdcall;
begin
  Result := 'OpenBLT using Vector CAN Interface';
end; //*** end of MbiDescription ***


//***************************************************************************************
// NAME:           MbiVersion
// PARAMETER:      none
// RETURN VALUE:   version number
// DESCRIPTION:    Called by the application to obtain the version number of the
//                 interface library.
//
//***************************************************************************************
function MbiVersion : Longword; stdcall;
begin
  Result := 10000; // v1.00.00
end; //*** end of MbiVersion ***


//***************************************************************************************
// NAME:           MbiVInterface
// PARAMETER:      none
// RETURN VALUE:   version number of the supported interface
// DESCRIPTION:    Called by the application to obtain the version number of the
//                 Mbi interface uBootInterface.pas (not the interface library). This can
//                 be used by the application for backward compatibility.
//
//***************************************************************************************
function MbiVInterface : Longword; stdcall;
begin
  Result := 10001; // v1.00.01
end; //*** end of MbiVInterface ***


//***************************************************************************************
// NAME:           MbiConfigure
// PARAMETER:      none
// RETURN VALUE:   none
// DESCRIPTION:    Called by the application to enable the user to configure the inter-
//                 face library through the application.
//
//***************************************************************************************
procedure MbiConfigure; stdcall;
var
  settings : TXcpSettings;
begin
  // create xcp settings object
  settings := TXcpSettings.Create(ExtractFilePath(ParamStr(0))+'openblt_can_vector.ini');

  // display the modal configuration dialog
  settings.Configure;

  // release the xcp settings object
  settings.Free;

  // update to the latest configuration
  loader.Configure(ExtractFilePath(ParamStr(0))+'openblt_can_vector.ini');
end; //*** end of MbiConfigure ***


//***************************************************************************************
// External Declarations
//***************************************************************************************
exports
  //--- begin of don't change ---
  MbiInit        index 1,
  MbiStart       index 2,
  MbiStop        index 3,
  MbiDeInit      index 4,
  MbiName        index 5,
  MbiDescription index 6,
  MbiVersion     index 7,
  MbiConfigure   index 8,
  MbiVInterface  index 9;
  //--- end of don't change ---

end.
//********************************** end of openblt_can_vector.dpr **********************
